﻿using System;
using System.Collections.Generic;
using System.Text;
using Windows.Networking.Sockets;
using JiveMessenger.Utilities;
using JiveMessenger;
using Windows.Storage.Streams;
using Newtonsoft.Json;
using Windows.Web;
using System.Threading.Tasks;
using JmShared;
using System.Diagnostics;
using Newtonsoft.Json.Converters;
using System.Threading;


namespace JiveMessenger.Network
{
    public enum WebSocketState
    {
        Open,
        Closed,
        Connecting
    }
    /// <summary>
    /// EventArgs for error and exception
    /// </summary>
    public class ErrorEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the exception.
        /// </summary>
        public Exception Exception { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ErrorEventArgs"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public ErrorEventArgs(string message)
        {
            Exception = new Exception(message);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ErrorEventArgs"/> class.
        /// </summary>
        /// <param name="exception">The exception.</param>
        public ErrorEventArgs(Exception exception)
        {
            Exception = exception;
        }
    }

    class JsonWebSocket
    {
        private Uri _url;
        private MessageWebSocket _messageWebSocket;
        private DataWriter _messageWriter;
        private static Random m_Random = new Random();
        private const string m_QueryTemplateA = "{0}-{1} {2}"; //With token and content
        private const string m_QueryTemplateB = "{0}-{1}"; //With token
        private const string m_QueryTemplateC = "{0} {1}"; //No token
        private const string m_QueryKeyTokenTemplate = "{0}-{1}";
        public WebSocketState State { get; set; }

        public JsonWebSocket(Uri url)
            : base()
        {
            _url = url;
        
        }

        async public Task Open()
        {
          _messageWebSocket = new MessageWebSocket();
            SubscribeEvents(_messageWebSocket);
            _messageWriter = new DataWriter(_messageWebSocket.OutputStream);
            // WebErrorStatus? status = null;
            try
            {
                this.State = WebSocketState.Connecting;
                //_url = new Uri("wss://echo.websocket.org");
                await _messageWebSocket.ConnectAsync(_url);
                this.State = WebSocketState.Open;
            }
            catch (Exception ex)
            {
                // status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
                this.State = WebSocketState.Closed;
                throw;
            }
            //finally
            //{
            //    if (status == null)
            //    {
            //        this.State = WebSocketState.Open;

            //        //connection is OK.
            //    }
            //    else
            //        this.State = WebSocketState.Closed;
            //}
        }

        private void SubscribeEvents(MessageWebSocket socket)
        {
            if (socket == null)
                return;
            socket.Closed += _messageWebSocket_Closed;
            socket.MessageReceived += _messageWebSocket_MessageReceived;
        }
        //private void UnSubscribeEvents(MessageWebSocket socket)
        //{
        //    //if (socket == null)
        //    //    return;
        //    //socket.Closed -= _messageWebSocket_Closed;
        //    //socket.MessageReceived -= _messageWebSocket_MessageReceived;
        //}
        void _messageWebSocket_MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
        {
            try
            {
                string message = string.Empty;
                using (DataReader reader = args.GetDataReader())
                {
                    reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                    message = reader.ReadString(reader.UnconsumedBufferLength);
                }
                if (string.IsNullOrEmpty(message))
                    return;

                int pos = message.IndexOf(' ');

                string name;
                string parameter;
                string token = string.Empty;

                if (pos > 0)
                {
                    name = message.Substring(0, pos);
                    parameter = message.Substring(pos + 1);

                    pos = name.IndexOf('-');

                    if (pos > 0)
                    {
                        token = name.Substring(pos + 1);
                        name = name.Substring(0, pos);
                    }
                }
                else
                {
                    name = message;
                    parameter = string.Empty;
                }

                IJsonExecutor executor = GetExecutor(name, token);

                if (executor == null)
                    return;

                object value;

                try
                {
                    if (!executor.Type.IsSimpleType())
                        value = DeserializeObject(parameter, executor.Type);
                    else if (parameter.GetType() == executor.Type)
                        value = parameter;
                    else
                        value = Convert.ChangeType(parameter, executor.Type, null);
                }
                catch (Exception exc)
                {
                    WebSocket_Error(this, new ErrorEventArgs(new Exception("DeserializeObject exception", exc)));
                    return;
                }

                try
                {
                    executor.Execute(this, token, value);
                }
                catch (Exception exce)
                {
                    WebSocket_Error(this, new ErrorEventArgs(new Exception("Message handling exception", exce)));
                }
            }
            catch (Exception ex) // For debugging
            {
                WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
                // Add your specific error-handling code here.
                _messageWebSocket.Close(1000, ex.Message);
            }


        }

        private EventHandler<ErrorEventArgs> m_Error;

        public event EventHandler<ErrorEventArgs> Error
        {
            add { m_Error += value; }
            remove { m_Error -= value; }
        }

        void WebSocket_Error(object sender, ErrorEventArgs e)
        {
            if (m_Error == null)
                return;

            m_Error(this, e);
        }

        private EventHandler<WebSocketClosedEventArgs> m_Closed;

        public event EventHandler<WebSocketClosedEventArgs> Closed
        {
            add { m_Closed += value; }
            remove { m_Closed -= value; }
        }
        void _messageWebSocket_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
        {
            this.State = WebSocketState.Closed;
            if (m_Closed == null)
                return;

            m_Closed(this, args);
           // UnSubscribeEvents(sender as MessageWebSocket);
            //MessageWebSocket webSocket = Interlocked.Exchange(ref _messageWebSocket, null);
            //if (webSocket != null)
            //{
            //    webSocket.Dispose();
            //}

        }


        public void Close()
        {
            if (_messageWebSocket == null)
                return;

            _messageWebSocket.Close(1000, "Closed by user");
        }

        /// <summary>
        /// Registers the message handler.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The message's name.</param>
        /// <param name="executor">The message handler.</param>
        public void On<T>(string name, Action<T> executor)
        {
            RegisterExecutor<T>(name, string.Empty, new JsonExecutor<T>(executor));
        }


        /// <summary>
        /// Registers the message handler.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <param name="executor">The executor.</param>
        public void On<T>(string name, Action<JsonWebSocket, T> executor)
        {
            RegisterExecutor<T>(name, string.Empty, new JsonExecutorWithSender<T>(executor));
        }



        /// <summary>
        /// Queries server.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The request name.</param>
        /// <param name="content">The request content.</param>
        /// <param name="executor">The response handler.</param>
        /// <returns>return token of the request</returns>
        public string Query<T>(string name, object content, Action<T> executor)
        {
            return Query<T>(name, content, new JsonExecutor<T>(executor));
        }

        /// <summary>
        /// Queries server.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The request name.</param>
        /// <param name="content">The request content.</param>
        /// <param name="executor">The response handler.</param>
        /// <returns>return token of the request</returns>
        public string Query<T>(string name, object content, Action<string, T> executor)
        {
            return Query<T>(name, content, new JsonExecutorWithToken<T>(executor));
        }

        /// <summary>
        /// Queries server.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The request name.</param>
        /// <param name="content">The request content.</param>
        /// <param name="executor">The response handler.</param>
        /// <returns></returns>
        public string Query<T>(string name, object content, Action<JsonWebSocket, T> executor)
        {
            return Query<T>(name, content, new JsonExecutorWithSender<T>(executor));
        }

        /// <summary>
        /// Queries server.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The request name.</param>
        /// <param name="content">The request content.</param>
        /// <param name="executor">The response handler.</param>
        /// <returns></returns>
        public string Query<T>(string name, object content, Action<JsonWebSocket, string, T> executor)
        {
            return Query<T>(name, content, new JsonExecutorFull<T>(executor));
        }

        /// <summary>
        /// Queries the specified name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <param name="content">The content.</param>
        /// <param name="executor">The executor.</param>
        /// <param name="state">The callback state.</param>
        /// <returns></returns>
        public string Query<T>(string name, object content, Action<JsonWebSocket, T, object> executor, object state)
        {
            return Query<T>(name, content, new JsonExecutorWithSenderAndState<T>(executor, state));
        }

        string Query<T>(string name, object content, IJsonExecutor executor)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");


            string token = new ShortGuid(Guid.NewGuid()).Value;// m_Random.Next(1000, 9999);

            RegisterExecutor<T>(name, token, executor);

            if (content != null)
            {
                if (!content.GetType().IsSimpleType())
                    _messageWriter.WriteString(string.Format(m_QueryTemplateA, name, token, SerializeObject(content)));
                else
                    _messageWriter.WriteString(string.Format(m_QueryTemplateA, name, token, content));
            }
            else
                _messageWriter.WriteString(string.Format(m_QueryTemplateB, name, token));
            _messageWriter.StoreAsync();
            return token;
        }



        /// <summary>
        /// Sends object with specific name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="content">The object you want to send.</param>
        public async Task Send(string name, object content)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            if (content != null)
            {
                if (!content.GetType().IsSimpleType())
                    _messageWriter.WriteString(string.Format(m_QueryTemplateC, name, SerializeObject(content)));
                else
                    _messageWriter.WriteString(string.Format(m_QueryTemplateC, name, content));
            }
            else
                _messageWriter.WriteString(name);

            await _messageWriter.StoreAsync();


        }

        /// <summary>
        /// Serializes the object.
        /// </summary>
        /// <param name="target">The target object is being serialized.</param>
        /// <returns></returns>
        protected virtual string SerializeObject(object target)
        {
            var settings = new JsonSerializerSettings()
            {
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
                ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
                ObjectCreationHandling = ObjectCreationHandling.Auto
            };
            settings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
            return JsonConvert.SerializeObject(target, settings);
        }

        /// <summary>
        /// Deserializes the json string to object.
        /// </summary>
        /// <param name="json">The json string.</param>
        /// <param name="type">The type of the target object.</param>
        /// <returns></returns>
        protected virtual object DeserializeObject(string json, Type type)
        {
            using (TimeIt t = new TimeIt("DeserializeObject of type " + type.Name))
            {
                var settings = new JsonSerializerSettings()
                {
                    TypeNameHandling = TypeNameHandling.None,
                    NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
                    ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
                };
                settings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
                return JsonConvert.DeserializeObject(json, type, settings);
            }
        }


        private Dictionary<string, IJsonExecutor> m_ExecutorDict = new Dictionary<string, IJsonExecutor>(StringComparer.OrdinalIgnoreCase);

        void RegisterExecutor<T>(string name, string token, IJsonExecutor executor)
        {
            lock (m_ExecutorDict)
            {
                if (string.IsNullOrEmpty(token))
                    m_ExecutorDict.Add(name, executor);
                else
                    m_ExecutorDict.Add(string.Format(m_QueryKeyTokenTemplate, name, token), executor);
            }
        }

        IJsonExecutor GetExecutor(string name, string token)
        {
            string key = name;
            bool removeExecutor = false;

            if (!string.IsNullOrEmpty(token))
            {
                key = string.Format(m_QueryKeyTokenTemplate, name, token);
                removeExecutor = true;
            }

            lock (m_ExecutorDict)
            {
                IJsonExecutor executor;

                if (!m_ExecutorDict.TryGetValue(key, out executor))
                    return null;

                if (removeExecutor)
                {
                    m_ExecutorDict.Remove(key);
                }

                return executor;
            }
        }
    }
}
